#include <QDateTime>
#include <QDir>
#include <QFile>
#include <QFileDialog>
#include <QFileInfo>
#include <QMessageBox>
#include <QSet>
#include <QSettings>
#include <QSqlDatabase>
#include <QSqlError>
#include <QSqlQuery>
#include <QSqlQueryModel>
#include <QTextStream>
#include <QTimeZone>
#include <QNetworkRequest>
#include <QUrl>
#include "qtvplugin_orbit.h"
#include "ui_qtvplugin_orbit.h"
#include <tuple>
// "coreLib.h" includes basic types from the core library,
// such as cSite, cJulian, etc. The header file also contains a
// "using namespace" statement for Zeptomoby::OrbitTools.
#include "coreLib.h"

// "orbitLib.h" includes basic types from the orbit library,
// including cOrbit.
#include "orbitLib.h"

void qtvplugin_orbit::on_pushButton_orbit_tle_update_clicked()
{
	save_ini();
	QDir dir_app(QCoreApplication::applicationDirPath());
	QString fileName = ui->lineEdit_orbit_tle_file->text();
	QUrl furl(fileName);
	QFileInfo info(fileName);
	if (info.exists())
	{
		fileName = dir_app.absoluteFilePath(fileName);
		QFile file(fileName);
		if (!file.open(QIODevice::ReadOnly))
		{
			QMessageBox::warning(this, tr("Error"), file.errorString());
			return;
		}
		ui->tabWidget_tledb->setDisabled(true);
		loadFromTextStream(file.readAll());
		ui->tabWidget_tledb->setEnabled(true);
		file.close();
	}
	else if (!furl.isValid()) {
		QMessageBox::warning(this, tr("Error"), tr("Invalid URL:")+ fileName);
		return;
	}
	else
	{
		ui->pushButton_orbit_tle_update->setDisabled(true);
		QNetworkRequest request(furl);
		m_pDownloadManager->get(request);
	}
}

void qtvplugin_orbit::on_download_finished(QNetworkReply * reply)
{
	if (reply->error()) {
		QString errMsg = reply->errorString();
		reply->deleteLater();
		QMessageBox::warning(this, tr("Error"), errMsg);
		ui->pushButton_orbit_tle_update->setEnabled(true);
		return;
	}
	// 将下载的数据写入文件
	const QByteArray data = reply->readAll();
	ui->tabWidget_tledb->setDisabled(true);
	loadFromTextStream(data);
	ui->tabWidget_tledb->setEnabled(true);
	reply->deleteLater();
	ui->pushButton_orbit_tle_update->setEnabled(true);
}

void qtvplugin_orbit::loadFromTextStream(QByteArray text)
{
	try
	{
		if (!confirm_tle_conn(false))
			return;
		const QString dbname = ini_file();
		QSqlDatabase db = QSqlDatabase::database(dbname);
		QSqlQuery query(db);

		QTextStream stm(&text);
		QString name, line1, line2;
		bool write = false;
		int total = 0;
		if (!db.transaction())
			throw db.lastError().text();
		while (!stm.atEnd())
		{
			QString line = stm.readLine();
			if (line.startsWith("1 "))
				line1 = line;
			else if (line.startsWith("2 "))
			{
				line2 = line;
				write = true;
			}
			else
				name = line;
			if (write)
			{
				write = false;
				QString strID = line1.mid(2, 5);
				QString strName = name.length() ? name : strID;
				QString strFmt = "3TLE";
				QString strText = name + "|" + line1 + "|" + line2;
				Zeptomoby::OrbitTools::cTle tle(strName.toStdString(),
												line1.toStdString(),
												line2.toStdString());
				cSatellite sat(tle);
				time_t epoch = sat.Orbit().Epoch().ToTime();
				QDateTime dtm = QDateTime::fromSecsSinceEpoch(epoch);

				QString insertSQL = "INSERT OR IGNORE into tle (sname, sepoch,stime, sid, sformat, stext) "
									"values (?,?,?,?,?,?);";
				if (!query.prepare(insertSQL))
					throw query.lastError().text();
				query.addBindValue(strName);
				query.addBindValue((unsigned long long) epoch);
				query.addBindValue(dtm.toString("yyyy-MM-ddTHH:mm:ss"));
				query.addBindValue(strID);
				query.addBindValue(strFmt);
				query.addBindValue(strText);
				if (!query.exec())
					throw query.lastError().text();

				++total;
				ui->label_orbit_import->setText(tr("%1 updated.").arg(total));
				QCoreApplication::processEvents();

			}
		}
		if (!db.commit())
			throw db.lastError().text();
		confirm_tle_conn(true);
		ui->label_orbit_import->setText("   ");
	}
	catch (QString msg)
	{
		QMessageBox::warning(this, tr("Error"), msg);
	}
	m_needUpdate = true;
}

void qtvplugin_orbit::on_pushButton_tle_orbit_del_clicked()
{
	auto rows = ui->tableView_orbit_tle_db_detail->selectionModel()->selectedRows();
	QVector<std::tuple<QString, qint64>	> delSet;
	foreach (auto v, rows)
	{
		QString sname = m_pTleModDetail->data(m_pTleModDetail->index(v.row(), 0)).toString();
		unsigned long long epoch = m_pTleModDetail->data(m_pTleModDetail->index(v.row(), 1))
									   .toULongLong();
		delSet << std::make_tuple(sname, epoch);
	}
	if (!confirm_tle_conn(false))
		return;
	const QString dbname = ini_file();
	QSqlDatabase db = QSqlDatabase::database(dbname);
	QSqlQuery query(db);
	try
	{
		QString querySql = "DELETE FROM tle where  sname = ? and sepoch = ? ";
		foreach (auto key, delSet)
		{
			if (!query.prepare(querySql))
				throw query.lastError().text();
			query.addBindValue(std::get<0>
							   (key));
			query.addBindValue(std::get<1>
							   (key));
			if (!query.exec())
				throw query.lastError().text();
		}
	}
	catch (QString msg)
	{
		QMessageBox::warning(this, tr("dberror"), msg);
	}
	confirm_tle_conn(true);
	m_pTleModDetail->clear();
	m_needUpdate = true;
}

void qtvplugin_orbit::on_pushButton_tle_orbit_del_sat_clicked()
{
	auto rows = ui->tableView_orbit_tle_db->selectionModel()->selectedRows();
	QSet<QString>
	namesToBeDel;
	foreach (auto v, rows)
	{
		QString sname = m_pTleMod->data(m_pTleMod->index(v.row(), 0)).toString();
		namesToBeDel << sname;
	}
	if (!confirm_tle_conn(false))
		return;
	const QString dbname = ini_file();
	QSqlDatabase db = QSqlDatabase::database(dbname);
	QSqlQuery query(db);
	try
	{
		QString querySql = "DELETE FROM tle where  sname = ? ";
		foreach (QString sname, namesToBeDel)
		{
			if (!query.prepare(querySql))
				throw query.lastError().text();
			query.addBindValue(sname);
			if (!query.exec())
				throw query.lastError().text();
		}
	}
	catch (QString msg)
	{
		QMessageBox::warning(this, tr("dberror"), msg);
	}
	confirm_tle_conn(true);
	m_pTleModDetail->clear();
	m_needUpdate = true;
}

void qtvplugin_orbit::on_toolButton_orbit_tle_update_clicked()
{
	QSettings settings(ini_file(), QSettings::IniFormat);
	QDir dir_app(QCoreApplication::applicationDirPath());
	QString last_dbdir = settings.value("orbit_db/tledir", "./").toString();
	last_dbdir = dir_app.absoluteFilePath(last_dbdir);
	QString fileName = QFileDialog::getOpenFileName(this,
													tr("Select TLE file"),
													last_dbdir,
													"TLE (*.tle);;TXT (*.txt);;All files (*.*)");
	if (fileName.length() > 2)
	{
		QFileInfo info(fileName);
		QString dirCurr = dir_app.relativeFilePath(info.absolutePath());
		settings.setValue("orbit_db/tledir", dirCurr);
		fileName = dir_app.relativeFilePath(fileName);
		ui->lineEdit_orbit_tle_file->setText(fileName);
		save_ini();
	}
	else
		ui->lineEdit_orbit_tle_file->setText("https://celestrak.org/NORAD/elements/gp.php?GROUP=active&FORMAT=tle");
}

void qtvplugin_orbit::init_tle_mod()
{
	m_pTleMod = new QSqlQueryModel;
	ui->tableView_orbit_tle_db->setModel(m_pTleMod);

	m_pTleModDetail = new QSqlQueryModel;
	ui->tableView_orbit_tle_db_detail->setModel(m_pTleModDetail);

	m_pDownloadManager = new QNetworkAccessManager(this);

	connect (m_pDownloadManager,&QNetworkAccessManager::finished,this,&qtvplugin_orbit::on_download_finished);

	confirm_tle_conn(true);
}

bool qtvplugin_orbit::confirm_tle_conn(bool reset_gui)
{
	try
	{
		const QString dbname = ini_file();
		if (QSqlDatabase::contains(dbname))
		{
			QSqlDatabase db = QSqlDatabase::database(dbname);
			if (db.isValid())
			{
				QSqlQuery q(db);
				QString testSql = "SELECT sname, sepoch,stime, sid, sformat, stext FROM tle WHERE "
								  "sid = '000'; ";
				if (!reset_gui)
				{
					if (q.exec(testSql))
						return true;
				}
				QString cond = ui->lineEdit_orbit_tledb_filter->text();

				QString querySql = QString("SELECT ")
								   + " sname as '" + tr("sname") + "'"
								   + ", sid as '" + tr("sid") + "'"
								   + ", max(stime) as '" + tr("max_stime") + "'"
								   + QString(" FROM tle where sname like '%%1%' OR sid like '%%1%'  group by sname, sid order by sname asc ").arg(cond);
				if (q.exec(querySql))
				{
					m_pTleMod->setQuery(std::move(q));
					return true;
				}
				db.close();
			}
			//FAILED
			QSqlDatabase::removeDatabase(dbname);
		}
		QSqlDatabase db = QSqlDatabase::addDatabase("QSQLITE", dbname);
		if (!db.isValid())
			throw db.lastError().text();
		QDir dir_app(QCoreApplication::applicationDirPath());
		QString fileName = ui->lineEdit_orbit_tle_db->text();
		fileName = dir_app.absoluteFilePath(fileName);
		db.setDatabaseName(fileName);
		if (!db.open())
			throw db.lastError().text();
		//Check table
		QString testSql
			= "SELECT sname, sepoch,stime, sid, sformat, stext FROM tle WHERE sid = '000'; ";
		QSqlQuery q(db);
		if (!q.exec(testSql))
		{
			QString drop_tbsql = " DROP TABLE tle ;";
			q.exec(drop_tbsql);
			QString create_tbsql = " CREATE TABLE IF NOT EXISTS tle ( "
								   " sname TEXT, "
								   " sid TEXT, "
								   " sepoch INTEGER , "
								   " stime TEXT , "
								   " sformat TEXT, "
								   " stext TEXT, "
								   " PRIMARY KEY (sname, sepoch) "
								   " );";
			if (!q.exec(create_tbsql))
				throw q.lastError().text();
			QString index_sql = " CREATE INDEX IF NOT EXISTS idx_sepoch ON tle (sepoch); \n ";
			if (!q.exec(index_sql))
				throw q.lastError().text();

			index_sql = " CREATE INDEX IF NOT EXISTS idx_sname ON tle (sname); \n ";
			if (!q.exec(index_sql))
				throw q.lastError().text();

			index_sql = " CREATE INDEX IF NOT EXISTS idx_stime ON tle (stime); \n ";
			if (!q.exec(index_sql))
				throw q.lastError().text();

			index_sql = " CREATE INDEX IF NOT EXISTS idx_sid ON tle (sid); \n ";
			if (!q.exec(index_sql))
				throw q.lastError().text();
		}
		if (reset_gui)
		{
			QString cond = ui->lineEdit_orbit_tledb_filter->text();
			QString querySql = QString("SELECT ")
							   + " sname as '" + tr("sname") + "'"
							   + ", sid as '" + tr("sid") + "'"
							   + ", max(stime) as '" + tr("max_stime") + "'"
							   + QString(" FROM tle where sname like '%%1%' OR sid like '%%1%'  group by sname, sid order by sname asc ").arg(cond);
			if (q.exec(querySql))
				m_pTleMod->setQuery(std::move(q));
			else
				throw q.lastError().text();
		}
	}
	catch (QString msg)
	{
		QMessageBox::warning(this, tr("dberror"), msg);
		return false;
	}

	return true;
}

void qtvplugin_orbit::on_tableView_orbit_tle_db_clicked(const QModelIndex &index)
{
	int r = index.row();
	QString sname = m_pTleMod->data(m_pTleMod->index(r, 0)).toString();
	if (!confirm_tle_conn(false))
		return;
	const QString dbname = ini_file();
	QSqlDatabase db = QSqlDatabase::database(dbname);
	QSqlQuery query(db);
	try
	{
		QString querySql = QString("SELECT ")
		+ " sname as '" + tr("sname") + "'"
			+ ", sepoch as '" + tr("sepoch") + "'"
			+ ", stime as '" + tr("stime") + "'"
			+ ", sformat as '"  + tr("sformat") + "'"
			+ ", stext as '" + tr("stext") + "'"
			+ " FROM tle where sname = ? order by sepoch desc ";
		if (!query.prepare(querySql))
			throw query.lastError().text();
		query.addBindValue(sname);
		if (!query.exec())
			throw query.lastError().text();
		m_pTleModDetail->setQuery(std::move(query));

		//LLA
		const QStringList lstTLE = m_pTleModDetail->data(m_pTleModDetail->index(0,4)).toString().split("|");
		std::string line1 = lstTLE.at(1).toStdString();
		std::string line2 = lstTLE.at(2).toStdString();
		cTle tle("name",line1,line2);
		cSatellite sat(tle);
		cGeoTime geo(sat.PositionEci(0));
		double lat = geo.LatitudeDeg();
		double lon = warp_to_180(geo.LongitudeDeg());
		double alt = geo.AltitudeKm()*1000;
		QString lb = QString(tr("LAT=%1, LON=%2, ALT=%3 m")).arg(lat).arg(lon).arg(alt);
		ui->label_orbit_tle_ep0lla->setText(lb);
	}
	catch (QString msg)
	{
		QMessageBox::warning(this, tr("dberror"), msg);
	}
}


void qtvplugin_orbit::on_pushButton_orbit_tle_find_clicked()
{
	save_ini();
	confirm_tle_conn(true);
}


void qtvplugin_orbit::on_pushButton_tle_orbit_track_add_clicked()
{
	save_ini();
	auto rows = ui->tableView_orbit_tle_db->selectionModel()->selectedRows();
	QSet<QString>	namesToBeInsert;
	foreach (auto v, rows)
	{
		QString sname = m_pTleMod->data(m_pTleMod->index(v.row(), 0)).toString();
		namesToBeInsert << sname;
	}
	if (!confirm_trace_conn(false))
		return;
	const QString dbname = ini_file();
	QSqlDatabase db = QSqlDatabase::database(dbname);
	QSqlQuery query(db);
	try
	{
		QString querySql = "INSERT OR IGNORE INTO trace (t_sname) values (?) ";
		foreach (QString sname, namesToBeInsert)
		{
			if (!query.prepare(querySql))
				throw query.lastError().text();
			query.addBindValue(sname);
			if (!query.exec())
				throw query.lastError().text();
		}
	}
	catch (QString msg)
	{
		QMessageBox::warning(this, tr("dberror"), msg);
	}
	confirm_trace_conn(true);
	ui->tabWidget_tledb->setCurrentIndex(1);
	m_needUpdate = true;
}


